﻿using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using DimensionsHelper.LanguageService.Protocol;
using DimensionsHelper.LanguageService.Symbols;
using DimensionsHelper.LanguageService.Syntax;
using ArgumentMap =
    System.Collections.Generic.Dictionary<string, DimensionsHelper.LanguageService.Syntax.MacroArgument>;

namespace DimensionsHelper.LanguageService;

public partial class Preprocessor
{
    /// <summary>
    ///     展开宏定义中的字符串化表达式，格式是"#StringizerName"。此方法会将参数宏调用表达式中的指定
    ///     参数节点列表合并并且转换为字符串。如果参数的节点列表中的节点之间包含大于1个空格，只保留1个空格。
    /// </summary>
    /// <param name="stringizer">字符串化表达式</param>
    /// <param name="arguments">参数宏的参数表</param>
    /// <returns>合并并转换后的参数字符串</returns>
    private SourceString ExpandStringizer(MacroStringizer stringizer, ArgumentMap arguments)
    {
        SourceString str = arguments.TryGetValue(stringizer.NameIdentifier.Text, out MacroArgument? value)
            ? NodeConverter.CombineAsString(null, value)
            : NodeFactory.CreateSourceString(null, stringizer.Start);
        str.SetUri(_source.Uri);
        return str;
    }


    /// <summary>
    ///     展开参数宏中标识符链接表达式。
    /// </summary>
    /// <param name="paster">标识符链接定义节点</param>
    /// <param name="arguments">参数宏调用表达式的参数表</param>
    /// <param name="diagnosticBag">错误传递对象</param>
    /// <returns>展开宏参数并且链接后的标识符</returns>
    /// <remarks>
    ///     如果其中某个参数是空，在进行连接时，视为空字符串
    /// </remarks>
    private SourceIdentifier ExpandTokenPaster(MacroTokenPaster paster, ArgumentMap arguments,
        ExpanderDiagnosticBag diagnosticBag)
    {
        SourceNode left = paster.Left;
        if (TryExpandMacroCallExpressionArgument(left, arguments, diagnosticBag, out MacroExpansion? expansion))
        {
            left = expansion.Count > 0
                ? NodeConverter.CombineAsIdentifier(expansion.ToArray<ITextNode>())
                : SourceNode.Default;
        }

        SourceNode right;
        if (paster.Right.IsNode(SyntaxKinds.MacroTokenPaster))
        {
            right = ExpandTokenPaster((MacroTokenPaster)paster.Right, arguments, diagnosticBag);
        }
        else
        {
            right = paster.Right;

            if (TryExpandMacroCallExpressionArgument(right, arguments, diagnosticBag,
                    out MacroExpansion? rightExpansion))
            {
                right = rightExpansion.Count > 0
                    ? NodeConverter.CombineAsIdentifier(rightExpansion.ToArray<ITextNode>())
                    : SourceNode.Default;
            }
        }

        SourceIdentifier id = NodeConverter.CombineAsIdentifier([left, right]);
        id.SetUri(_source.Uri);
        return id;
    }


    /// <summary>
    ///     尝试展开参数参数定义列表中的参数节点。
    ///     此方法会检查节点名是否在参数列表中或者宏符号表中，如果是可变参数，需要是 __VA_ARGS__。
    /// </summary>
    /// <param name="source">参数宏定义中的参数标识符，例如：#define A(B, C) B + C 中的'B'和'C'</param>
    /// <param name="argMap">参数宏调用表达式的参数表</param>
    /// <param name="diagnosticBag">错误传递对象</param>
    /// <param name="expansion">展开后的节点</param>
    private bool TryExpandMacroCallExpressionArgument(SourceNode source, ArgumentMap argMap,
        ExpanderDiagnosticBag diagnosticBag, [MaybeNullWhen(false)] out MacroExpansion expansion)
    {
        expansion = null;

        // 参数宏的参数名是标识符或者'__VA_ARGS__'
        if (!source.IsOneOfNode(SyntaxKinds.Identifier))
        {
            return false;
        }

        // 检查参数列表
        if (argMap.TryGetValue(source.Text, out MacroArgument? arg))
        {
            expansion = NodeFactory.CreateMacroExpansion(null, source);
            ExpandDefinitionNodeList(arg, expansion, diagnosticBag, argMap);
            return true;
        }

        // 检查宏定义
        if (!_macroSymbolTable.TryGetSymbol(source.Text, out ISymbol? macroSymbol))
        {
            return false;
        }

        expansion = ExpandNormalMacro(source, (MacroSymbol)macroSymbol, diagnosticBag);
        return true;
    }


    // "Str"MACRO -> 将"Str"合并到MACRO中，并扩展MACRO的范围
    private static void ExpandStringToConnectedMacro(SourceNode strNode,
        MacroExpansion expansion)
    {
        SourceNode? first = expansion.First;
        Debug.Assert(first != null && first.IsNode(SyntaxKinds.String));
        strNode.IsMacroExpansionSkipped = true;
        // 更新第一个字符串节点
        first.Update(strNode.Text + "\"" + first.Text);
        // 更新展开节点
        expansion.Update(strNode.Start, expansion.End - strNode.Start);
    }


    // MACRO"Str" -> 将"Str"合并到MACRO中，并扩展MACRO的范围
    private static void ExpandConnectedMacro(MacroExpansion lastExpansion, SourceNode node)
    {
        SourceNode? last = lastExpansion.Last;
        Debug.Assert(last != null && last.IsNode(SyntaxKinds.String));
        node.IsParserSkipped = true;
        // 更新展开节点列表中的最后一个
        last.Update(last.Text + "\"" + node.Text);
        // 更新展开节点
        lastExpansion.Update(lastExpansion.Start, node.End - lastExpansion.Start);
    }


    /// <summary>
    ///     对于展开后连接在一起的节点，需要考虑字符串相连的情况，例如：
    ///     <code>
    /// #define STR "TEMP"
    /// Dim a, b
    /// a = STR"-STR"
    /// b = "STR-"STR
    /// </code>
    ///     此时，a和b的赋值表达式中，右值在宏展开后，会出现两个字符串连在一起的情况，
    ///     而两个双引号'""'会变成字符串中的双引号转义，因此，需要合并成一个节点。
    /// </summary>
    /// <param name="targetList">目标预处理节点列表</param>
    /// <param name="node">需要添加的宏展开节点</param>
    /// <returns>如果展开节点紧跟字符串并进行了合并，返回<see langword="true" />，否则，返回<see langword="false" />。</returns>
    private bool TryExpandConnectedStringAndMacro(PreNodeList targetList, SourceNode node)
    {
        if (!targetList.Any())
        {
            return false;
        }

        SourceNode last = targetList.Last();

        // 如果两个节点不相连，不可合并节点
        if (last.End != node.Start)
        {
            return false;
        }

        // 如果上一个节点是字符并且和宏展开节点相连，检查是否可以合并字符串
        // 如果可以合并，将上一个字符串并入宏展开节点，并更新宏扩展节点起始位置
        if (last.IsNode(SyntaxKinds.String) &&
            node.Is<MacroExpansion>(out MacroExpansion? expansion))
        {
            SourceNode? expansionFirst = expansion.First;
            // 如果宏展开的第一个节点是字符串，同文件的上一个字符串合并，在中间添加一个双引号
            if (expansionFirst?.IsNode(SyntaxKinds.String) == true)
            {
                ExpandStringToConnectedMacro(last, expansion);
                // 替换上一个节点
                targetList.NodeListInternal[^1] = expansion;
                FinishToken(targetList, targetList.Start, node.End - targetList.Start);
                return true;
            }
        }
        // 如果上一个节点是宏扩展节点，新加的节点是字符串，并且两个节点相连，检查是否可以合并字符串
        else if (last.Is<MacroExpansion>(out expansion) && node.IsNode(SyntaxKinds.String))
        {
            node.IsParserSkipped = true;
            SourceNode? expansionLast = expansion.Last;
            // 宏展开的最后一个节点是字符串，才可以合并
            if (expansionLast?.IsNode(SyntaxKinds.String) == true)
            {
                ExpandConnectedMacro(expansion, node);
                FinishToken(targetList, targetList.Start, node.End - targetList.Start);
                return true;
            }
        }

        return false;
    }


    /// <summary>
    ///     将宏展开列表添加到_processedFile节点列表的末尾，此方法会对紧接的字符串
    ///     进行检查，如果是展开后相连的字符串，会将相连的节点合并。
    /// </summary>
    /// <param name="expansion">待添加的宏展开节点</param>
    private void NormalizeToFile(MacroExpansion expansion)
    {
        if (!TryExpandConnectedStringAndMacro(_processedFile, expansion))
        {
            _processedFile.AddNode(expansion);
            _processedFile.Update(_processedFile.Start, expansion.End);
        }

        _processedFile.AddMacroExpansion(expansion);
    }


    /// <summary>
    ///     将节点追加到目标原始节点列表中，此方法会检查相连的字符串节点，
    ///     如果是，会扩展宏展开节点范围，覆盖两侧的字符串。
    /// </summary>
    /// <param name="targetList">目标节点列表</param>
    /// <param name="node">待添加的原始节点</param>
    private void NormalizeNodeToList(PreNodeList targetList, SourceNode node)
    {
        if (!TryExpandConnectedStringAndMacro(targetList, node))
        {
            targetList.AddNode(node);
        }
    }


    private static MacroExpansion CreateMacroExpansionFrom(MacroExpansion source)
    {
        MacroExpansion res = source.Source.IsIdentifier
            ? NodeFactory.CreateMacroExpansion(null, source.Source)
            : NodeFactory.CreateMacroExpansion(null, (MacroCallExpression)source.Source);
        res.UpdateFrom(source);
        res.SetUri(source.Uri);
        return res;
    }


    /// <summary>
    ///     将嵌套的宏展开转换为单层列表。
    /// </summary>
    /// <param name="expansion">已有的宏展开列表</param>
    private MacroExpansion FlattenExpansion(MacroExpansion expansion)
    {
        if (!expansion.IsNested)
        {
            return expansion;
        }

        MacroExpansion res = CreateMacroExpansionFrom(expansion);
        res.ExpansionByStep = expansion;
        foreach (SourceNode node in expansion)
        {
            if (node.Is<MacroExpansion>(SyntaxKinds.MacroExpansion, out MacroExpansion? exp))
            {
                MacroExpansion flattenExpansion = FlattenExpansion(exp);
                int start = 0;
                if (TryExpandConnectedStringAndMacro(res, flattenExpansion))
                {
                    start = 1;
                }

                for (int i = start; i < exp.Count; i++)
                {
                    SourceNode child = (SourceNode)exp[i].Copy();
                    child.Update(node.Start, node.End);
                    NormalizeNodeToList(res, child);
                }
            }
            else
            {
                SourceNode nodeCopy = (SourceNode)node.Copy();
                nodeCopy.Update(node.Start, node.End);
                NormalizeNodeToList(res, nodeCopy);
            }
        }

        return res;
    }


    /// <summary>
    ///     展开宏定义列表中指定索引开始的节点，如果指定位置的节点是宏，将展开并返回展开后的结果。
    /// </summary>
    /// <param name="list">预处理节点列表</param>
    /// <param name="index">参数字段</param>
    /// <param name="diagnosticBag">报错对象</param>
    /// <param name="argMap">传递的参数表</param>
    private SourceNode ExpandDefinitionNode(IReadOnlyList<SourceNode> list, ref int index,
        ExpanderDiagnosticBag diagnosticBag, ArgumentMap? argMap)
    {
        SourceNode node = list[index];

        if (!node.IsIdentifier)
        {
            return (SourceNode)node.Copy();
        }

        // 优先匹配参数表
        if (argMap?.TryGetValue(node.Text, out MacroArgument? macroArg) == true)
        {
            MacroExpansion expansion = NodeFactory.CreateMacroExpansion(null, node);
            ExpandDefinitionNodeList(macroArg, expansion, diagnosticBag, null /* 参数表不可传递 */);
            return expansion;
        }

        // 检查宏定义
        if (!_macroSymbolTable.TryGetSymbol(node.Text, out ISymbol? macro))
        {
            return (SourceNode)node.Copy();
        }

        // 非参数宏
        if (!macro.IsParameter)
        {
            return ExpandNormalMacro(node, (MacroSymbol)macro, diagnosticBag);
        }

        // 参数宏
        int nextIndex = index + 1;
        if (nextIndex >= list.Count || !list[nextIndex].IsNode(SyntaxKinds.LeftParenthesisToken))
        {
            return (SourceNode)node.Copy();
        }

        MacroCallExpression callExpr = ParseCallExpressionFromDefinitionList(list, ref index, diagnosticBag);
        return ExpandParameterMacro(callExpr, (MacroSymbol)macro, diagnosticBag, argMap);
    }


    private void ExpandDefinitionNodeList(IReadOnlyList<SourceNode> list, MacroExpansion target,
        ExpanderDiagnosticBag diagnosticBag, ArgumentMap? argMap)
    {
        int index = 0;
        while (index < list.Count)
        {
            target.AddNode(ExpandDefinitionNode(list, ref index, diagnosticBag, argMap));
            index++;
        }
    }


    private void ExpandDefinitionNodeList(IReadOnlyList<SourceNode> list, ArgumentMap argMap,
        MacroExpansion target, ExpanderDiagnosticBag diagnosticBag)
    {
        int index = 0;
        while (index < list.Count)
        {
            SourceNode node = list[index];
            if (node.IsNode(SyntaxKinds.MacroStringizer))
            {
                target.AddNode(ExpandStringizer((MacroStringizer)node, argMap));
            }
            else if (node.IsNode(SyntaxKinds.MacroTokenPaster))
            {
                target.AddNode(ExpandTokenPaster((MacroTokenPaster)node, argMap, diagnosticBag));
            }
            else if (node.IsIdentifier && argMap.TryGetValue(node.Text, out MacroArgument? argument))
            {
                MacroExpansion expand = NodeFactory.CreateMacroExpansion(null, node);
                ExpandDefinitionNodeList(argument, expand, diagnosticBag, argMap);
                target.AddNode(expand);
            }
            else
            {
                target.AddNode(ExpandDefinitionNode(list, ref index, diagnosticBag, argMap));
            }

            index++;
        }
    }


    /// <summary>
    ///     展开非参数宏。此方法只展开一步，内部的嵌套宏递归展开。
    ///     此方法不检查宏的有效性，相关检查应在调用此方法前进行。
    /// </summary>
    /// <param name="id">标识符节点</param>
    /// <param name="macroSymbol">宏符号</param>
    /// <param name="diagnosticBag">错误传递对象</param>
    private MacroExpansion ExpandNormalMacro(SourceNode id, MacroSymbol macroSymbol,
        ExpanderDiagnosticBag? diagnosticBag = null)
    {
        bool firstLevel = diagnosticBag == null;
        ExpanderDiagnosticBag bag = diagnosticBag ?? new ExpanderDiagnosticBag(null);
        Macro decl = macroSymbol.GetLastDeclaration();
        id.IsMacroSymbolReferenced = true;
        MacroExpansion exp = NodeFactory.CreateMacroExpansion(null, id);
        // 展开列表
        ExpandDefinitionNodeList(decl.Definitions, exp, bag, null);
        // 添加报错
        if (firstLevel && bag.DiagnosticInfo != null)
        {
            Raise(bag.DiagnosticInfo);
        }

        return FinishToken(exp, id.Start, id.Width, id.Text);
    }


    /// <summary>
    ///     从定义节点列表解析参数宏调用表达式，需要调用此方法时<paramref name="index" />所在的
    ///     节点是标识符，下一个节点是左圆括号<see cref="SyntaxKinds.LeftParenthesisToken" />。
    ///     如果解析成功，最终会停在右圆括号<see cref="SyntaxKinds.RightParenthesisToken" />上。
    ///     此方法只对进行参数区分，不展开可能存在的宏。
    /// </summary>
    /// <param name="list">定义列表</param>
    /// <param name="index">索引字段</param>
    /// <param name="diagnosticBag">错误传递对象</param>
    private MacroCallExpression ParseCallExpressionFromDefinitionList(IReadOnlyList<SourceNode> list, ref int index,
        ExpanderDiagnosticBag diagnosticBag)
    {
        SourceNode callee = list[index];
        SourceNode lp = list[++index];
        SourceNode? rp = null;
        ++index;

        List<MacroArgument> argList = new();
        MacroArgument? currentArg = null;

        while (index < list.Count)
        {
            SourceNode node = list[index];
            // , | )
            if (currentArg == null || node.IsOneOfNode(SyntaxKinds.CommaToken, SyntaxKinds.RightParenthesisToken))
            {
                if (currentArg != null)
                {
                    FinishToken(currentArg, currentArg.Start,
                        currentArg.Count > 0
                            ? currentArg[^1].End - currentArg.Start
                            : node.Start - currentArg.Start);
                    currentArg.Update(node);
                }

                // 遇到右括号结束
                if (node.IsNode(SyntaxKinds.RightParenthesisToken))
                {
                    rp = node;
                    break;
                }

                // 记录参数
                currentArg = NodeFactory.CreateMacroArgument(null);
                currentArg.Update(node.Start);
                argList.Add(currentArg);
            }

            if (!node.IsNode(SyntaxKinds.CommaToken))
            {
                currentArg.AddNode((SourceNode)node.Copy());
            }

            ++index;
        }

        int callExprEnd = callee.End;
        MacroArgumentList args = NodeFactory.CreateMacroArgumentList(null, lp, rp, argList);

        if (rp == null)
        {
            diagnosticBag.Raise(DiagnosticDescriptors.UnexpectedToken,
                _source.Uri,
                argList.Count > 0 ? argList[^1].End : lp.End,
                1, ")");
        }

        if (argList.Count > 0 || rp != null)
        {
            callExprEnd = rp?.End ?? argList[^1].End;
            FinishToken(args, _lexer.GetText(lp.Start, callExprEnd - lp.Start));
        }

        MacroCallExpression expr = NodeFactory.CreateMacroCallExpression(null, callee, args);
        return FinishToken(expr, callee.Start, callExprEnd - callee.Start);
    }


    /// <summary>
    ///     对照参数宏调用表达式和宏符号定义，对传入的参数进行参数名分组。
    /// </summary>
    /// <param name="expr">宏调用表达式</param>
    /// <param name="symbol">宏符号</param>
    private ArgumentMap GroupMacroArguments(MacroCallExpression expr, MacroSymbol symbol)
    {
        ArgumentMap res = new();
        int index = 0;
        while (index < expr.Arguments?.Count)
        {
            MacroParameterSymbol? parameter = symbol.GetParameter(index);
            if (parameter == null)
            {
                break;
            }

            MacroArgument arg = expr.Arguments[index];
            if (!parameter.IsVaArgs)
            {
                res.Add(parameter.Name, arg);
            }
            else
            {
                const string vaArgsKey = "__VA_ARGS__";
                if (!res.TryGetValue(vaArgsKey, out MacroArgument? macroArg))
                {
                    macroArg = NodeFactory.CreateMacroArgument(null);
                    res.Add(vaArgsKey, macroArg);
                }

                macroArg.AddNode(arg);
                if (arg.TrailingCommaToken != null)
                {
                    macroArg.AddNode(arg.TrailingCommaToken);
                }

                if (macroArg.Count > 0)
                {
                    FinishToken(macroArg, macroArg[0].Start, macroArg[^1].End - macroArg[0].Start);
                }
            }

            ++index;
        }

        return res;
    }


    private static void GetExpansionInnerNodes(List<SourceNode> target, MacroExpansion expansion)
    {
        foreach (SourceNode? node in expansion)
        {
            if (node.IsMacroExpansion)
            {
                GetExpansionInnerNodes(target, (MacroExpansion)node);
            }
            else
            {
                target.Add(node);
            }
        }
    }


    /// <summary>
    ///     展开宏调用表达式中可能存在的宏，并重新解析成新的表达式。
    /// </summary>
    /// <param name="callExpr">当前表达式</param>
    /// <param name="argMap">传递的参数表</param>
    /// <param name="diagnosticBag">错误信息对象</param>
    private void ReParseMacroCallExpression(ref MacroCallExpression callExpr, ArgumentMap argMap,
        ExpanderDiagnosticBag diagnosticBag)
    {
        List<SourceNode> nodeList = new()
        {
            callExpr.Callee
        };

        if (callExpr.Arguments?.LeftParenthesisToken != null)
        {
            nodeList.Add(callExpr.Arguments.LeftParenthesisToken);
        }

        bool needReParse = false;

        if (callExpr.Arguments != null)
        {
            foreach (MacroArgument? arg in callExpr.Arguments)
            {
                MacroExpansion argExpansion = NodeFactory.CreateMacroExpansion(null, arg);
                ExpandDefinitionNodeList(arg, argExpansion, diagnosticBag, argMap);
                if (argExpansion.Count > 0)
                {
                    FinishToken(argExpansion, argExpansion[0].Start,
                        argExpansion[^1].End - argExpansion[0].Start);
                }

                List<SourceNode> innerNodes = new();
                GetExpansionInnerNodes(innerNodes, argExpansion);
                if (arg.TrailingCommaToken != null)
                {
                    nodeList.Add(arg.TrailingCommaToken);
                }

                if (argExpansion.IsNested)
                {
                    needReParse = true;
                }
            }
        }

        if (!needReParse)
        {
            return;
        }

        if (callExpr.Arguments?.RightParenthesisToken != null)
        {
            nodeList.Add(callExpr.Arguments.RightParenthesisToken);
        }

        int index = 0;
        callExpr = ParseCallExpressionFromDefinitionList(nodeList, ref index, diagnosticBag);
    }


    /// <summary>
    ///     展开参数宏。此方法内只展开一步，内部的嵌套宏递归展开。
    ///     此方法不检查宏的有效性，相关检查应在调用此方法前进行。
    /// </summary>
    /// <param name="callExpr">参数宏调用表达式</param>
    /// <param name="macroSymbol">宏符号</param>
    /// <param name="diagnosticBag">错误传递对象</param>
    /// <param name="passedArgs">传递的参数表</param>
    private MacroExpansion ExpandParameterMacro(MacroCallExpression callExpr,
        MacroSymbol macroSymbol, ExpanderDiagnosticBag? diagnosticBag = null,
        ArgumentMap? passedArgs = null)
    {
        bool firstLevel = diagnosticBag == null;
        ExpanderDiagnosticBag bag = diagnosticBag ?? new ExpanderDiagnosticBag(null);

        // 如果传入了参数表，尝试展开参数中的宏
        if (passedArgs != null && callExpr.Arguments?.Count > 0)
        {
            ReParseMacroCallExpression(ref callExpr, passedArgs, bag);
        }

        // 检查参数个数
        if (!macroSymbol.CanAcceptParametersLength(callExpr.Arguments?.Count ?? 0))
        {
            bag.Raise(DiagnosticDescriptors.MacroGetsErrorArgumentsLength,
                callExpr, macroSymbol.Name, macroSymbol.ParametersCount.ToString());
            ReportDiagnostic();
            return MacroExpansion.Empty;
        }

        MacroExpansion exp = NodeFactory.CreateMacroExpansion(null, callExpr);

        if (macroSymbol.ParametersCount > 0 && callExpr.Arguments != null)
        {
            ArgumentMap map = GroupMacroArguments(callExpr, macroSymbol);
            ExpandDefinitionNodeList(macroSymbol.GetLastDeclaration().Definitions, map, exp, bag);
        }

        ReportDiagnostic();
        return exp;

        void ReportDiagnostic()
        {
            if (firstLevel && bag.DiagnosticInfo != null)
            {
                Raise(bag.DiagnosticInfo);
            }
        }
    }


    private class ExpanderDiagnosticBag(DiagnosticInfo? diagnostic)
    {
        public DiagnosticInfo? DiagnosticInfo { get; private set; } = diagnostic;


        private void AddDiagnostic(DiagnosticInfo info)
        {
            if (DiagnosticInfo == null)
            {
                DiagnosticInfo = info;
            }
            else
            {
                DiagnosticInfo.Related.Add(info);
            }
        }


        public void Raise(DiagnosticDescriptor descriptor, ITextNode node, params object?[] args)
        {
            DiagnosticInfo diag = new(
                SyntaxLocation.Create(node.Uri, node.Start, node.Width), descriptor, args);
            AddDiagnostic(diag);
        }


        public void Raise(DiagnosticDescriptor descriptor, DocumentUri uri, int start, int length,
            params object?[] args)
        {
            DiagnosticInfo diag = new(SyntaxLocation.Create(uri, start, length), descriptor, args);
            AddDiagnostic(diag);
        }
    }
}